/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
*/

const GCM_NB:usize=4;
const GCM_ACCEPTING_HEADER:usize=0;
const GCM_ACCEPTING_CIPHER:usize=1;
const GCM_NOT_ACCEPTING_MORE:usize=2;
const GCM_FINISHED:usize=3;
const GCM_ENCRYPTING:usize=0;
const GCM_DECRYPTING:usize=1;

use aes;
use aes::AES;

pub struct GCM {
	table: [[u32;4];128],
	statex: [u8;16],
	y_0: [u8;16],
//	counter: usize,
	lena:[u32;2],
	lenc:[u32;2],
	status:usize,
	a:AES
}

impl GCM {

	fn pack(b: [u8;4]) -> u32 { /* pack bytes into a 32-bit Word */
        return ((((b[0])&0xff) as u32)<<24)|((((b[1])&0xff) as u32)<<16)|((((b[2])&0xff) as u32)<<8)|(((b[3])&0xff) as u32);
	}
  
	fn unpack(a: u32) -> [u8;4] { /* unpack bytes from a word */
        let b:[u8;4]=[((a>>24)&0xff) as u8,((a>>16)&0xff) as u8,((a>>8)&0xff) as u8,(a&0xff) as u8];
		return b;
	}

	fn precompute(&mut self,h: &[u8]) {
		let mut b:[u8;4]=[0;4];
        let mut j=0;
        for i in 0..GCM_NB {
            b[0]=h[j]; b[1]=h[j+1]; b[2]=h[j+2]; b[3]=h[j+3];
            self.table[0][i]=GCM::pack(b);
            j+=4;
        }
        for i in 1..128 {
	    	let mut c:u32=0;
            for j in 0..GCM_NB {self.table[i][j]=c|(self.table[i-1][j])>>1; c=self.table[i-1][j]<<31;}
            if c != 0  {self.table[i][0]^=0xE1000000} /* irreducible polynomial */
        }          
	}

	fn gf2mul(&mut self) { /* gf2m mul - Z=H*X mod 2^128 */
        let mut p:[u32;4]=[0;4]; 
    
        for i in 0..4 {p[i]=0}
        let mut j:usize=8; 
        let mut m=0;
        for i in 0..128 {
	    	j-=1;
            let mut c=((self.statex[m]>>j)&1) as u32; c= (!c) +1;
	    	for k in 0..GCM_NB {p[k]^=self.table[i][k]&c}
            if j==0 {
				j=8; m+=1;
                if m==16 {break}
            }
        }
        j=0;
        for i in 0..GCM_NB {
            let b=GCM::unpack(p[i]);
            self.statex[j]=b[0]; self.statex[j+1]=b[1]; self.statex[j+2]=b[2]; self.statex[j+3]=b[3];
            j+=4;
        }
	}

	fn wrap(&mut self) { /* Finish off GHASH */
        let mut f:[u32;4]=[0;4]; 
        let mut el:[u8;16]=[0;16];  
   
    /* convert lengths from bytes to bits */
        f[0]=(self.lena[0]<<3)|(self.lena[1]&0xE0000000)>>29;
        f[1]=self.lena[1]<<3;
        f[2]=(self.lenc[0]<<3)|(self.lenc[1]&0xE0000000)>>29;
        f[3]=self.lenc[1]<<3;
        let mut j=0;
        for i in 0..GCM_NB {
            let b=GCM::unpack(f[i]);
            el[j]=b[0]; el[j+1]=b[1]; el[j+2]=b[2]; el[j+3]=b[3];
            j+=4;
        }
        for i in 0..16 {self.statex[i]^=el[i]}
        self.gf2mul();
	}

	fn ghash(&mut self,plain: &[u8],len: usize) -> bool {
        if self.status==GCM_ACCEPTING_HEADER {self.status=GCM_ACCEPTING_CIPHER}
        if self.status != GCM_ACCEPTING_CIPHER {return false}
        
        let mut j=0;
        while j<len {
            for i in 0..16 {
            	if j>=len {break}
				self.statex[i]^=plain[j]; j+=1;
                self.lenc[1]+=1; if self.lenc[1]==0 {self.lenc[0]+=1}
            }
            self.gf2mul();
        }
        if len%16 != 0 {self.status=GCM_NOT_ACCEPTING_MORE}
        return true;
    }

    /* Initialize GCM mode */
	pub fn init(&mut self,nk: usize,key: &[u8],niv: usize,iv: &[u8]) { /* iv size niv is usually 12 bytes (96 bits). AES key size nk can be 16,24 or 32 bytes */
		let mut h:[u8;16]=[0;16];
    
        for i in 0..16 {h[i]=0; self.statex[i]=0}
        
		self.a=AES::new();

        self.a.init(aes::ECB,nk,key,None);
        self.a.ecb_encrypt(&mut h);    /* E(K,0) */
        self.precompute(&h);
        
        self.lena[0]=0;self.lenc[0]=0;self.lena[1]=0;self.lenc[1]=0;
        if niv==12 {
            for i in 0..12 {self.a.f[i]=iv[i]}
            let b=GCM::unpack(1);
            self.a.f[12]=b[0]; self.a.f[13]=b[1]; self.a.f[14]=b[2]; self.a.f[15]=b[3];  /* initialise IV */
            for i in 0..16 {self.y_0[i]=self.a.f[i]}
        } else {
            self.status=GCM_ACCEPTING_CIPHER;
            self.ghash(iv,niv); /* GHASH(H,0,IV) */
            self.wrap();
            for i in 0..16 {self.a.f[i]=self.statex[i];self.y_0[i]=self.a.f[i];self.statex[i]=0}
            self.lena[0]=0;self.lenc[0]=0;self.lena[1]=0;self.lenc[1]=0;
        }
        self.status=GCM_ACCEPTING_HEADER;
	}

	pub fn new() -> GCM {
		GCM {
			table:[[0;4];128],
			statex:[0;16],
			y_0:[0;16],
			//counter:0,
			lena:[0;2],
			lenc:[0;2],
			status:0,
			a:AES::new()
		}
	}

/* Add Header data - included but not encrypted */
	pub fn add_header(&mut self,header: &[u8],len: usize) -> bool { /* Add some header. Won't be encrypted, but will be authenticated. len is length of header */
        if self.status != GCM_ACCEPTING_HEADER {return false}
        let mut j=0;
        while j<len {
            for i in 0..16 {
            	if j>=len {break}
				self.statex[i]^=header[j]; j+=1;
                self.lena[1]+=1; if self.lena[1]==0 {self.lena[0]+=1}
            }
            self.gf2mul();
        }
        if len%16 != 0 {self.status=GCM_ACCEPTING_CIPHER}
        return true;
    }

/* Add Plaintext - included and encrypted */
	pub fn add_plain(&mut self,cipher: &mut [u8],plain: &[u8],len: usize) -> bool {
		let mut cb:[u8;16]=[0;16]; 
		let mut b:[u8;4]=[0;4];
       
        let mut counter: u32;
        if self.status == GCM_ACCEPTING_HEADER {self.status=GCM_ACCEPTING_CIPHER}
        if self.status != GCM_ACCEPTING_CIPHER {return false}
        
        let mut j=0;
        while j<len {
            b[0]=self.a.f[12]; b[1]=self.a.f[13]; b[2]=self.a.f[14]; b[3]=self.a.f[15];
            counter=GCM::pack(b);
            counter+=1;
            b=GCM::unpack(counter);
            self.a.f[12]=b[0]; self.a.f[13]=b[1]; self.a.f[14]=b[2]; self.a.f[15]=b[3]; /* increment counter */
            for i in 0..16 {cb[i]=self.a.f[i]}
            self.a.ecb_encrypt(&mut cb);        /* encrypt it  */
    
            for i in 0..16 {
            	if j>=len {break}
				cipher[j]=plain[j]^cb[i];
				self.statex[i]^=cipher[j]; j+=1;
                self.lenc[1]+=1; if self.lenc[1]==0 {self.lenc[0]+=1}
            }
            self.gf2mul()
        }
        if len%16 != 0 {self.status=GCM_NOT_ACCEPTING_MORE}
        return true;
	}

/* Add Ciphertext - decrypts to plaintext */
	pub fn add_cipher(&mut self,plain: &mut [u8],cipher: &[u8],len: usize) -> bool {
		let mut cb:[u8;16]=[0;16]; 
		let mut b:[u8;4]=[0;4];
        
       let mut counter: u32;
        
        if self.status==GCM_ACCEPTING_HEADER {self.status=GCM_ACCEPTING_CIPHER}
        if self.status != GCM_ACCEPTING_CIPHER {return false}
    
        let mut j=0;
        while j<len {
            b[0]=self.a.f[12]; b[1]=self.a.f[13]; b[2]=self.a.f[14]; b[3]=self.a.f[15];
            counter=GCM::pack(b);
            counter+=1;
            b=GCM::unpack(counter);
            self.a.f[12]=b[0]; self.a.f[13]=b[1]; self.a.f[14]=b[2]; self.a.f[15]=b[3]; /* increment counter */
            for i in 0..16 {cb[i]=self.a.f[i]}
            self.a.ecb_encrypt(&mut cb);        /* encrypt it  */
            for i in 0..16 {
            	if j>=len {break}
				let oc=cipher[j];
				plain[j]=cipher[j]^cb[i];
				self.statex[i]^=oc; j+=1;
                self.lenc[1]+=1; if self.lenc[1]==0 {self.lenc[0]+=1}
            }
            self.gf2mul()
        }
        if len%16 != 0 {self.status=GCM_NOT_ACCEPTING_MORE}
        return true;
	}

/* Finish and extract Tag */
	pub fn finish(&mut self,extract: bool) -> [u8;16]  { /* Finish off GHASH and extract tag (MAC) */
		let mut tag:[u8;16]=[0;16]; 
    
        self.wrap();
        /* extract tag */
        if extract {
            self.a.ecb_encrypt(&mut (self.y_0));        /* E(K,Y0) */
            for i in 0..16 {self.y_0[i]^=self.statex[i]}
            for i in 0..16 {tag[i]=self.y_0[i];self.y_0[i]=0;self.statex[i]=0}
        }
        self.status=GCM_FINISHED;
        self.a.end();
        return tag;
	}

	pub fn hex2bytes(hex: &[u8],bin: &mut [u8]) {
		let len=hex.len();

		for i in 0..len/2 {
			let mut v:u8;
        	let mut c = hex[2*i];
        	if c >= b'0' && c <= b'9' {
            	v = c - b'0';
        	} else if c >= b'A' && c <= b'F' {
            	v = c - b'A' + 10;
        	} else if c >= b'a' && c <= b'f' {
            	v = c - b'a' + 10;
        	} else {
            	v = 0;
        	}
        	v <<= 4;
        	c = hex[2*i + 1];
        	if c >= b'0' && c <= b'9' {
            	v += c - b'0';
        	} else if c >= b'A' && c <= b'F' {
            	v += c - b'A' + 10;
        	} else if c >= b'a' && c <= b'f' {
            	v += c - b'a' + 10;
        	} else {
            	v = 0;
        	}
        	bin[i] = v;
    	}
	}

}
/*
fn main()
{
	let kt=b"feffe9928665731c6d6a8f9467308308";
	let mt=b"d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39";
	let ht=b"feedfacedeadbeeffeedfacedeadbeefabaddad2";
	let nt=b"9313225df88406e555909c5aff5269aa6a7a9538534f7da1e4c303d2a318a728c3c0c95156809539fcf0e2429a6b525416aedbf5a0de6a57a637b39b";
// Tag should be 619cc5aefffe0bfa462af43c1699d050

	let mut gcm=GCM::new();

	let len=mt.len()/2;
	let lenh=ht.len()/2;
	let lenk=kt.len()/2;
	let leniv=nt.len()/2;

	//let mut t:[u8;16]=[0;16];	// Tag
	let mut k:[u8;16]=[0;16];   // AES Key
	let mut h:[u8;64]=[0;64];  	// Header - to be included in Authentication, but not encrypted
	let mut n:[u8;100]=[0;100];	// IV - Initialisation vector
	let mut m:[u8;100]=[0;100];	// Plaintext to be encrypted/authenticated
	let mut c:[u8;100]=[0;100];	// Ciphertext
	let mut p:[u8;100]=[0;100];	// Recovered Plaintext 

	GCM::hex2bytes(mt,&mut m);
	GCM::hex2bytes(ht,&mut h);
	GCM::hex2bytes(kt,&mut k);
	GCM::hex2bytes(nt,&mut n);

 	println!("Plaintext=");
	for i in 0..len {print!("{:02x}",m[i])}
	println!("");

	gcm.init(lenk,&k,leniv,&n);
	
	gcm.add_header(&h,lenh);
	gcm.add_plain(&mut c,&m,len);
	let mut t=gcm.finish(true);

 	println!("Ciphertext=");
	for i in 0..len {print!("{:02x}",c[i])}
	println!("");
  
 	println!("Tag=");
	for i in 0..16 {print!("{:02x}",t[i])}
	println!("");

	gcm.init(lenk,&k,leniv,&n);
	
	gcm.add_header(&h,lenh);
	gcm.add_cipher(&mut p,&c,len);
	t=gcm.finish(true);

 	println!("Plaintext=");
	for i in 0..len {print!("{:02x}",p[i])}
	println!("");

	println!("Tag=");
	for i in 0..16 {print!("{:02x}",t[i])}
	println!("");

}
*/
